package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io/fs"
	"math/rand"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

type item struct {
	m4sPath  []string
	infoPath string
}

func (item item) deal(outpath string) error {
	if len(item.m4sPath) != 2 {
		return fmt.Errorf("unpredictable number of m4s files, %d", len(item.m4sPath))
	}
	var tmpPathList []string
	for _, path := range item.m4sPath {
		tmppath, err := modifyM4S(path)
		if err != nil {
			return fmt.Errorf("modifyM4s audio failed, %v", err)
		}
		tmpPathList = append(tmpPathList, tmppath)
	}

	if item.infoPath == "" {
		return fmt.Errorf("missing info file")
	}
	videoInfo, err := extractTitle(item.infoPath)
	if err != nil {
		return fmt.Errorf("extractTitle failed, %s, %v", item.infoPath, err)
	}

	randStr := ""
	fulloutpath := ""
	for {
		fulloutpath = filepath.Join(outpath, randStr+fmt.Sprintf("%.3d", videoInfo.P)+"_"+videoInfo.Title+".mp4")
		if !isExist(fulloutpath) {
			break
		}
		randStr = fmt.Sprintf("%.6f", rand.Float64())
	}

	cmd := exec.Command("ffmpeg", "-i", tmpPathList[0], "-i", tmpPathList[1], "-c", "copy", fulloutpath)
	err = cmd.Run()

	if err != nil {
		return fmt.Errorf(`cmd.Run failed ,"%s" , %v\n`, cmd.String(), err)
	}

	err = os.Remove(tmpPathList[0])
	if err != nil {
		return fmt.Errorf("os.Remove audio failed, %v", err)
	}
	err = os.Remove(tmpPathList[1])
	if err != nil {
		return fmt.Errorf("os.Remove video failed, %v", err)
	}

	return nil
}

type videoInfo struct {
	Title string `json:"title"`
	P     int    `json:"p"`
}

func main() {
	// 定义命令行标志
	m4spath := flag.String("i", "", "path to where the m4s file is located or a higher level path")
	outpath := flag.String("o", "", "generated mp4 file path")
	// 解析命令行参数
	flag.Parse()

	if *m4spath == "" || *outpath == "" {
		fmt.Println(`please use "m4sMerge - h" for more information`)
		return
	}
	if !isExist(*m4spath) {
		fmt.Printf("path does not exist, %s\n", *m4spath)
		return
	}
	if !isExist(*outpath) {
		fmt.Printf("path does not exist, %s\n", *outpath)
		return
	}
	mitem := make(map[string]*item)
	filepath.WalkDir(*m4spath, func(path string, d fs.DirEntry, err error) error {
		if d.IsDir() {
			return nil
		}
		if strings.Contains(path, "tmp_") {
			return nil
		}
		dir, file := filepath.Split(path)
		it, ok := mitem[dir]
		if ok {
			if strings.Contains(file, ".m4s") {
				it.m4sPath = append(it.m4sPath, path)
			} else if strings.Contains(file, ".videoInfo") {
				it.infoPath = path
			}
		} else {
			has := false
			it2 := &item{}
			if strings.Contains(file, ".m4s") {
				it.m4sPath = append(it.m4sPath, path)
				has = true
			} else if strings.Contains(file, ".videoInfo") {
				it2.infoPath = path
				has = true
			}
			if has {
				mitem[dir] = it2
			}
		}
		return nil
	})
	for _, i := range mitem {
		err := i.deal(*outpath)
		if err != nil {
			fmt.Println(err)
			return
		}
	}
	fmt.Println("Done")
}

func checkM4sFormat(buf []byte) error {
	if len(buf) < 0x2c {
		return fmt.Errorf("incorrect m4s file format")
	}
	for i := 0; i < 9; i++ {
		if buf[i] != 0x30 {
			return fmt.Errorf("incorrect m4s file format")
		}
	}
	if buf[0xc] != 0x24 {
		return fmt.Errorf("incorrect m4s file format")
	}
	if !bytes.Equal(buf[0x19:0x1D], []byte{0x61, 0x76, 0x63, 0x31}) {
		return fmt.Errorf("incorrect m4s file format")
	}
	return nil
}
func modifyM4S(path string) (newpath string, err error) {
	buf, err := os.ReadFile(path)
	if err != nil {
		err = fmt.Errorf("os.ReadFile failed, %v", err)
		return
	}
	err = checkM4sFormat(buf)
	if err != nil {
		err = fmt.Errorf("checkM4sFormat failed, %s, %v", path, err)
		return
	}

	buf = buf[9:]
	buf[3] = 0x20
	copy(buf[10:], buf[14:])
	buf = buf[0 : len(buf)-4]

	dir, file := filepath.Split(path)
	newpath = filepath.Join(dir, "tmp_"+file)
	err = os.WriteFile(newpath, buf, os.ModePerm)
	if err != nil {
		err = fmt.Errorf("os.WriteFile failed, %v", err)
		return
	}
	return
}
func extractTitle(path string) (*videoInfo, error) {
	vi := &videoInfo{}
	buf, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("os.ReadFile failed, %v", err)
	}
	err = json.Unmarshal(buf, vi)
	if err != nil {
		return nil, fmt.Errorf("json.Unmarshal failed, %v", err)
	}
	return vi, nil
}
func isExist(filePath string) bool {
	_, err := os.Stat(filePath)
	return !os.IsNotExist(err)
}
